﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GameDataTempshitEditor.Widgets;
using Red.GameData;
using System.Drawing;
using System.Diagnostics;

namespace GameDataTempshitEditor.DataEditors
{
    /// <summary>
    /// Property item matching a single editable property in the source data
    /// </summary>
    public class GameData_PropertyItem : Widgets.PropertyItemBase
    {
        /// <summary>
        /// Constructor for normal element
        /// </summary>
        public GameData_PropertyItem(PropertyItemBase parent, EditorContext context, EditorPropertyView propInfo, bool canEdit)
            : base(parent, propInfo.Name)
        {
            _Context = context;
            _ArrayIndex = -1;
            _PropInfo = propInfo;
            _CanEdit = canEdit;

            // we can expand only if there's a valid object
            _CanExpand = _PropInfo.IsObject && _PropInfo.IsInlined && (_PropInfo.CachedDisplayObject != null);
        }

        /// <summary>
        /// Constructor for array element
        /// </summary>
        public GameData_PropertyItem(PropertyItemBase parent, EditorContext context, EditorPropertyView propInfo, int arrayindex, bool canEdit)
            : base(parent, String.Format("[{0}]", arrayindex))
        {
            _Context = context;
            _ArrayIndex = arrayindex;
            _PropInfo = propInfo;
            _CanEdit = canEdit;

            // we can expand only if there's a valid object
            _CanExpand = _PropInfo.IsObject && _PropInfo.IsInlined && (_PropInfo.CachedDisplayObject != null);
        }

        public EditorPropertyView PropInfo
        {
            get
            {
                return _PropInfo;
            }
        }

        private GameData_PropertyArray ParentArray
        {
            get
            {
                if (_ArrayIndex != -1)
                    return (GameData_PropertyArray)Parent;

                return null;
            }
        }

        public override bool IsOverriden()
        {
            return _PropInfo.CachedIsOverriden;
        }

        public override bool IsReadOnly()
        {
            return _PropInfo.IsReadOnly || !_PropInfo.CachedIsWritable || !_CanEdit;
        }

        public override bool IsItalic()
        {
            return _PropInfo.CachedIsDefaultValue;
        }

        public override bool IsStrikedOut()
        {
            return (_PropInfo.IsArrayElement && _PropInfo.CachedIsSuppressed);
        }

        public override bool HasErrors()
        {
            return (_PropInfo.CachedNoValue && !_PropInfo.IsOptional && _PropInfo.CachedIsWritable && !_PropInfo.IsReadOnly && !_PropInfo.CachedIsDefaultValue);
        }

        private string _LocalCachedObjectName;

        public override PropertyItemBase.RefreshResult RefreshThis()
        {
            base.RefreshThis();

            // object case
            if (_PropInfo.IsObject && !_PropInfo.IsArray)
            {
                // we can expand only if there's a valid object
                _CanExpand = _PropInfo.IsInlined && (_PropInfo.CachedDisplayObject != null);

                // do we need to refresh the object ?
                if (_LocalCachedObjectName != _PropInfo.CachedValue)
                {
                    // update local display cache
                    _LocalCachedObjectName = _PropInfo.CachedValue;

                    // refresh items
                    var wasExpanded = IsExpanded;
                    Collapse();
                    if (wasExpanded && _CanExpand)
                        Expand();

                    // inform called that the whole sub tree was refreshed
                    return RefreshResult.TreeRefreshed;
                }
            }           
      
            return RefreshResult.ItemRefreshed;
        }

        public override string DisplayValue()
        {
            return _PropInfo.CachedDisplayValue;
        }

        public override void CreateElementActions(ref List<PropertyItemAction> actions)
        {
            // not editable
            if (IsReadOnly())
                return;

            // non-key overridden values can be reset
            if (IsOverriden() && (_PropInfo.Name != _PropInfo.ReflectionProp.Parent.KeyName))
            {
                actions.Add(new PropertyItemAction_Button(this, "Reset", Action_ResetPropertyOverride));
            }

            // new object
            var classType = _PropInfo.ReflectionProp.Type as Red.GameData.Raw.ReflectionClass;
            if (classType != null)
            {
                if (_PropInfo.IsInlined)
                {
                    actions.Add(new PropertyItemAction_Button(this, "Replace", Action_CreateInplaceObject));

                    // do we have local value ?
                    if ( _PropInfo.CachedIsOverriden )
                    {
                        if (_PropInfo.CachedDisplayObject != null)
                        {
                            actions.Add(new PropertyItemAction_Button(this, "Destroy", Action_DeleteInplaceObject));

                            // object is not parented by us - we will have to copy it to edit
                            if (_PropInfo.CachedDisplayObject.ParentID != _PropInfo.Object.RawData.ID)
                                actions.Add(new PropertyItemAction_Button(this, "Copy", Action_CopyInplaceObject));
                        }
                    }
                    else if ( !_PropInfo.CachedNoValue )
                    {
                        if (_PropInfo.CachedDisplayObject != null)
                        {
                            actions.Add(new PropertyItemAction_Button(this, "Copy", Action_CopyInplaceObject));
                            //actions.Add(new PropertyItemAction_Button(this, "Derive", Action_DeleteInplaceObject));
                        }
                    }
                }
            }

            // array
            if (_ArrayIndex != -1)
            {
                if (IsOverriden())
                {
                    actions.Add(new PropertyItemAction_Button(this, "Delete", Action_Array_DeleteElement));

                    actions.Add(new PropertyItemAction_Button(this, "D", Action_Array_Down));
                    actions.Add(new PropertyItemAction_Button(this, "U", Action_Array_Up));                    
                }
                else
                {
                    if (_PropInfo.CachedIsSuppressed)
                        actions.Add(new PropertyItemAction_Button(this, "Unsuppress", Action_Array_UnsuppressElement));
                    else
                        actions.Add(new PropertyItemAction_Button(this, "Suppress", Action_Array_SuppressElement));
                }
            }

            // custom editors
            if (_PropInfo.ReflectionProp.CustomEditor.StartsWith("ResourcePath:") )
            {
                actions.Add(new PropertyItemAction_Button(this, "...", Action_PickDepotFile));                
            }
        }

        bool Action_PickDepotFile(PropertyItemBase item, ref string errorString)
        {
            var depotPath = Properties.Settings.Default.DepotPath;
            if (depotPath == "")
            {
                errorString = "Please setup depot path in the Settings before using the file picker";
                return false;
            }

            // unify path
            depotPath = depotPath.Replace('/', '\\' );

            // make sure it ends with path separator
            if ( !depotPath.EndsWith("\\") )
                depotPath += "\\";

            var ext = _PropInfo.ReflectionProp.CustomEditor.Substring("ResourcePath:".Length);            

            var openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
            openFileDialog1.Title = "Select file";
            openFileDialog1.DefaultExt = ext;
            openFileDialog1.Filter = "Resource files [*." + ext + "]|*." + ext;

            // restore previously selected file
            if (_PropInfo.CachedValue != "")
            {
                openFileDialog1.FileName = System.IO.Path.GetFileName(_PropInfo.CachedValue);
                openFileDialog1.InitialDirectory = depotPath + System.IO.Path.GetDirectoryName(_PropInfo.CachedValue);
            }
            else
            {
                openFileDialog1.InitialDirectory = depotPath;
            }

            // allow user to select new file
            if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                if (openFileDialog1.FileName != _PropInfo.CachedValue)
                {
                    if (openFileDialog1.FileName.StartsWith(depotPath))
                    {
                        var localPath = openFileDialog1.FileName.Substring(depotPath.Length);
                        return SetValue(null, localPath, ref errorString);
                    }
                    else
                    {
                        errorString = "Selected file is outside depot";
                        return false;
                    }
                }
            }

            // nothing changed
            return true;
        }

        bool Action_Array_Up(PropertyItemBase item, ref string errorString)
        {
            if (!_PropInfo.IsArrayElement)
            {
                errorString = "Not an array element";
                return false;
            }

            // create and dispatch action
            var action = Red.GameData.Actions.MoveArrayElementUp.Create(_Context, _PropInfo.Object.RawData, _PropInfo.Name, _PropInfo.ValueID);
            return _Context.ActionHistory.Execute(action, ref errorString);
        }

        bool Action_Array_Down(PropertyItemBase item, ref string errorString)
        {
            if (!_PropInfo.IsArrayElement)
            {
                errorString = "Not an array element";
                return false;
            }

            // create and dispatch action
            var action = Red.GameData.Actions.Action_MoveArrayElementDown.Create(_Context, _PropInfo.Object.RawData, _PropInfo.Name, _PropInfo.ValueID);
            return _Context.ActionHistory.Execute(action, ref errorString);
        }

        bool Action_Array_DeleteElement(PropertyItemBase item, ref string errorString)
        {
            if (!_PropInfo.IsArrayElement)
            {
                errorString = "Not an array element";
                return false;
            }

            // create and dispatch action
            var action = Red.GameData.Actions.RemoveArrayElement.Create(_Context, _PropInfo.Object.RawData, _PropInfo.Name, _PropInfo.ValueID);
            return _Context.ActionHistory.Execute(action, ref errorString);
        }

        bool Action_Array_SuppressElement(PropertyItemBase item, ref string errorString)
        {
            if (!_PropInfo.IsArrayElement)
            {
                errorString = "Not an array element";
                return false;
            }

            // create and dispatch action
            var action = Red.GameData.Actions.SuppressArrayElement.Create(_Context, _PropInfo.Object.RawData, _PropInfo.Name, _PropInfo.ValueID);
            return _Context.ActionHistory.Execute(action, ref errorString);
        }

        bool Action_Array_UnsuppressElement(PropertyItemBase item, ref string errorString)
        {
            if (!_PropInfo.IsArrayElement)
            {
                errorString = "Not an array element";
                return false;
            }

            // create and dispatch action
            var action = Red.GameData.Actions.UnsuppressArrayElement.Create(_Context, _PropInfo.Object.RawData, _PropInfo.Name, _PropInfo.ValueID);
            return _Context.ActionHistory.Execute(action, ref errorString);
        }

        bool Action_ResetPropertyOverride(PropertyItemBase item, ref string errorString)
        {
            if (_ArrayIndex != -1)
            {
                var valueId = _PropInfo.ValueID;
                Debug.Assert(valueId != Guid.Empty); // array elements are expected to have value ID

                // create and dispatch action
                var action = Red.GameData.Actions.RemoveOverride.Create(_Context, _PropInfo.Object.RawData, _PropInfo.Name, valueId);
                return _Context.ActionHistory.Execute(action, ref errorString);
            }
            else
            {
                // create and dispatch action
                var action = Red.GameData.Actions.RemoveOverride.Create(_Context, _PropInfo.Object.RawData, _PropInfo.Name, Guid.Empty);
                return _Context.ActionHistory.Execute(action, ref errorString);
            }
        }

        bool Action_CreateInplaceObject(PropertyItemBase item, ref string errorString)
        {
            var baseClass = _PropInfo.ReflectionProp.Type as Red.GameData.Raw.ReflectionClass;

            ClassPicker picker = new ClassPicker();
            var selectedClass = picker.SelectClass(baseClass, baseClass);
            if (selectedClass == null)
                return true;

            // create the new, inlined object in the same file as the current object
            var parentObject = _PropInfo.Object.RawData;
            var newObject = _PropInfo.Object.RootDataCollection.Action_NewUnnamedObject(_Context.Reflection, parentObject, selectedClass.Name);
            if (newObject == null)
            {
                errorString = "Unable to create new object";
                return false;
            }
            // set the "value" of the property
            var valueToSet = Red.GameData.Raw.Utils.FormatGuid(newObject.ID);
            if (!SetValue(null, valueToSet, ref errorString))
                return false;

            // resolve
            Collapse();
            Expand();
            return true;
        }

        bool Action_CopyInplaceObject(PropertyItemBase item, ref string errorString)
        {
            var baseClass = _PropInfo.ReflectionProp.Type as Red.GameData.Raw.ReflectionClass;

            // get current object
            var currentObject = _PropInfo.CachedDisplayObject;
            if (currentObject == null)
            {
                errorString = "Unable to copy non-existing object";
                return false;
            }

            // create the new, inlined object in the same file as the current object
            var parentObject = _PropInfo.Object.RawData;
            var newObject = _PropInfo.Object.RootDataCollection.Action_CopyObject(parentObject, currentObject);
            if (newObject == null)
            {
                errorString = "Unable to create new object";
                return false;
            }

            // set the "value" of the property
            var valueToSet = Red.GameData.Raw.Utils.FormatGuid(newObject.ID);
            if (!SetValue(null, valueToSet, ref errorString))
                return false;

            // resolve
            Collapse();
            Expand();
            return true;
        }

        bool Action_DeleteInplaceObject(PropertyItemBase item, ref string errorString)
        {
            // set the "value" of the property
            var valueToSet = "";
            if (!SetValue(null, valueToSet, ref errorString))
                return false;

            // resolve
            Collapse();
            Expand();
            return true;
        }

        public override bool SetValue(PropertyItemEditor fromEditor, string value, ref string errorMessage)
        {
            if (_ArrayIndex != -1)
            {
                var valueId = _PropInfo.ValueID;
                Debug.Assert( valueId != Guid.Empty ); // array elements are expected to have value ID

                // create and dispatch action
                var action = Red.GameData.Actions.SetValue.Create( _Context, _PropInfo.Object.RawData, _PropInfo.Name, value, valueId );
                return _Context.ActionHistory.Execute( action, ref errorMessage );
            }
            else
            {
                // create and dispatch action
                var action = Red.GameData.Actions.SetValue.Create(_Context, _PropInfo.Object.RawData, _PropInfo.Name, value, Guid.Empty );
                return _Context.ActionHistory.Execute(action, ref errorMessage);
            }
        }

        public override PropertyItemEditor CreateEditor()
        {
            // generate best editor based on the type
            if (_PropInfo.ReflectionType.Name == "int")
            {
                return new PropertyItemEditor_Int(this);
            }
            else if (_PropInfo.ReflectionType.Name == "float")
            {
                return new PropertyItemEditor_Float(this);
            }
            else if (_PropInfo.ReflectionType.Name == "string")
            {
                return new PropertyItemEditor_Text(this);
            }
            else if (_PropInfo.ReflectionType.Name == "Vector4" || _PropInfo.ReflectionType.Name == "Vector3" || _PropInfo.ReflectionType.Name == "Vector2" || _PropInfo.ReflectionType.Name == "EulerAngles" || _PropInfo.ReflectionType.Name == "Color")
            {
                return new PropertyItemEditor_Text(this);
            }
            else if (_PropInfo.ReflectionType.Name == "name")
            {
                return new PropertyItemEditor_Name(this);
            }
            else if (_PropInfo.ReflectionType.Name == "bool")
            {
                if (_PropInfo.IsReadOnly)
                    return null;

                return new PropertyItemEditor_Bool(this);
            }
            else if (_PropInfo.IsObject && !_PropInfo.IsInlined)
            {
                var classType = _PropInfo.ReflectionProp.Type as Red.GameData.Raw.ReflectionClass;
                return new GameData_PropertyEditor_ObjectList(this, classType, _PropInfo.Object.RootReflection, _PropInfo.Object.RootDataCollection);
            }
            else if (_PropInfo.IsEnum)
            {
                var enumType = _PropInfo.ReflectionProp.Type as Red.GameData.Raw.ReflectionEnum;
                return new GameData_PropertyEditor_Enum(this, enumType);
            }

            return null;
        }

        public override bool ExpandElement(ref List<PropertyItemBase> newElements)
        {
            // create the visualizer for the sub object
            if ( _PropInfo.IsInlined && (_PropInfo.CachedDisplayObject != null) )
            {
                var subObjectView = EditorObjectView.CreateView( _Context, _PropInfo.CachedDisplayObject );
                var isParentedByUs = (subObjectView.RawData.ParentID == _PropInfo.Object.RawData.ID);
                var canEdit = IsOverriden() && _CanEdit && isParentedByUs; // we can only edit object that are parented by us!
                newElements.Add(new GameData_PropertyGroup_Root(this, _Context, subObjectView, canEdit));
                return true;
            }

            return false;
        }

        private readonly EditorContext _Context; // edition context
        private readonly EditorPropertyView _PropInfo; // the property (source of data)
        private int _ArrayIndex; // index of the element in case it's an array
        private bool _CanEdit;
    }


}
